/*
 * @(#)ProviderFunctions.java 1.0 2000/12/14
 * 
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 * 
 * This software is the proprietary information of Thomas Barnekow. Use is
 * subject to license terms.
 */

package jessx;

import java.util.*;
import jess.*;

import tb.util.*;
import tb.provider.*;
import jessx.provider.*;

/**
 * This {@link jess.Userpackage}integrates the <b>Jess Fact Storage Provider
 * Framework</b> (see package {@link jessx.provider}) into the Jess language.
 * It defines the following functions:
 * <p>
 * 
 * <b><i>(fsm-do-backward-chaining &lt;entity-name&gt; [&lt;fact-name&gt;]):
 * </i></b>
 * <ul>
 * Activate backward chaining for the given entity. The qualified
 * &lt;entity-name&gt; (e.g., the table name "scott.emp") can be an atom, a
 * string, or a {@link tb.util.QualifiedName}. If &lt;fact-name&gt; is omitted
 * it will be computed from &lt;entity-name&gt; by replacing the dot (".") with
 * a dash ("-"). For instance, "scott.emp" yields "scott-dept".
 * </ul>
 * 
 * <b><i>(fsm-describe &lt;entity-name&gt; [&lt;service&gt;]):</i></b>
 * <ul>
 * Return a {@link MetaObject}instance describing the entity (e.g., a schema
 * or table) denoted by &lt;entity-name&gt;. If &lt;service&gt; is omitted,
 * <code>MetaDataProvider.DEFAULT_DESCRIPTION</code> is used as the default
 * value.
 * </ul>
 * 
 * <b><i>(fsm-define (&lt;tb.provider.MetaObject&gt; |
 * &lt;multifield-value&gt;)*):</i></b>
 * <ul>
 * Define deftemplate(s) for the given {@link MetaObject}or list of
 * meta-objects.
 * </ul>
 * 
 * <b><i>(fsm-define-backchain-reactive (&lt;tb.provider.MetaObject&gt; |
 * &lt;multifield-value&gt;)*):</i></b>
 * <ul>
 * Define backchain-reactive deftemplate(s) for the given {@link MetaObject}or
 * list of meta-objects.
 * </ul>
 * 
 * <b><i>(fsm-select &lt;goal-fact&gt;+):</i></b>
 * <ul>
 * Query the {@link jessx.provider.FactReader}serving the entity corresponding
 * to the &lt;goal-fact&gt; and assert the resulting facts. The function
 * returns a list of {@link jess.Fact}instances.
 * </ul>
 * 
 * <b><i>(fsm-assert &lt;fact&gt;+):</i></b>
 * <ul>
 * Store the given &lt;fact&gt; using the
 * {@link jessx.provider.FactWriter FactWriter}serving the corresponding
 * entity. The function returns the stored {@link jess.Fact}or a list of
 * stored facts.
 * </ul>
 * 
 * <b><i>(fsm-retract &lt;fact-id&gt;+):</i></b>
 * <ul>
 * Delete the fact identified by &lt;fact-id&gt; from persistent storage, using
 * the {@link jessx.provider.FactWriter}serving the corresponding entity. The
 * function returns the deleted {@link jess.Fact}or the list of deleted facts.
 * </ul>
 * 
 * @author Thomas Barnekow
 * @version 1.0
 * @see jessx.provider
 */
public class ProviderFunctions implements Userpackage {

	/**
	 * Function name: "fsm-do-backward-chaining"
	 */
	public static final String DO_BACKWARD_CHAINING =
		"fsm-do-backward-chaining";

	/**
	 * Function name: "fsm-select"
	 */
	public static final String SELECT = "fsm-select";

	/**
	 * Function name: "fsm-assert"
	 */
	public static final String ASSERT = "fsm-assert";

	/**
	 * Function name: "fsm-retract"
	 */
	public static final String RETRACT = "fsm-retract";

	/**
	 * Function name: "fsm-describe"
	 */
	public static final String DESCRIBE = "fsm-describe";

	/**
	 * Function name: "fsm-define"
	 */
	public static final String DEFINE = "fsm-define";

	/**
	 * Function name: "fsm-define-backchain-reactive"
	 */
	public static final String DEFINE_BACKCHAIN_REACTIVE =
		"fsm-define-backchain-reactive";

	/**
	 * The one and only engine per ProviderFunctions instance.
	 */
	Rete m_engine;

	/**
	 * Userfunction handles
	 */
	Map m_uf = new HashMap();

	/**
	 * Add this package
	 */
	public void add(Rete engine) {
		if (m_engine != null && m_engine != engine) {
			throw new RuntimeException("ProviderFunctions can't be shared among multiple engines");
		}
		m_engine = engine;

		// Require AdviceFunctions and JotsFunctions
		require(new AdviceFunctions(), AdviceFunctions.EXPAND, engine);
		require(new JotsFunctions(), JotsFunctions.CURRENT_BEGIN, engine);

		// Add own functions
		engine.addUserfunction(new DoBackwardChaining());
		engine.addUserfunction(new Select());
		engine.addUserfunction(new FactWriterFunction(ASSERT));
		engine.addUserfunction(new FactWriterFunction(RETRACT));

		engine.addUserfunction(new Describe());
		engine.addUserfunction(new Define(DEFINE));
		engine.addUserfunction(new Define(DEFINE_BACKCHAIN_REACTIVE));
	}

	/**
	 * Require a given set of functions.
	 */
	private void require(Userpackage up, String sample, Rete engine) {
		if (engine.findUserfunction(sample) == null)
			engine.addUserpackage(up);
	}

	/*
	 * Utility methods
	 */

	private QualifiedName getQualifiedName(
		Value value,
		Context context,
		String delimiter)
		throws JessException {
		int type = value.type();

		if (type == RU.ATOM || type == RU.STRING)
			return new QualifiedName(value.stringValue(context), delimiter);
		else if (type == RU.EXTERNAL_ADDRESS)
			return (QualifiedName) value.externalAddressValue(context);
		else
			throw new JessException(
				"DoBackwardChaining.getQualifiedName",
				"Illegal parameter value",
				value.toString());
	}

	/**
	 * FactStorageManager.doBackwardChaining as a Userfunction
	 */
	class DoBackwardChaining implements Userfunction {

		public String getName() {
			return DO_BACKWARD_CHAINING;
		}

		/**
		 * The Userfunction will be called as follows: <br>
		 * (fsm-do-backward-chaining <i>entity-name</i>[<i>fact-name</i>])
		 */
		public Value call(ValueVector vvec, Context context)
			throws JessException {
			if (vvec.size() < 2)
				throw new JessException(
					"DoBackwardChaining.call",
					"Error: wrong number of parameters",
					vvec.toStringWithParens());

			QualifiedName entityName =
				getQualifiedName(
					vvec.get(1),
					context,
					QualifiedName.DEFAULT_DELIMITER);

			String factName =
				vvec.size() < 3
					? entityName.toString(Template.DELIMITER)
					: vvec.get(2).stringValue(context);

			FactStorageManager.doBackwardChaining(
				entityName,
				factName,
				context.getEngine());
			return Funcall.TRUE;
		}
	}

	/**
	 * This <code>Userfunction</code> is used in order to read backward
	 * chaining reactive facts from an external knowledge base.
	 * 
	 * @author Thomas Barnekow
	 * @version 1.0
	 */
	class Select implements Userfunction {

		/**
		 * My name is Bond, James Bond...
		 */
		public String getName() {
			return SELECT;
		}

		/**
		 * Usage: (fsm-select &lt;goal&gt;+)
		 */
		public Value call(ValueVector vvec, Context context)
			throws JessException {
			int argc = vvec.size();
			if (argc < 2) {
				throw new JessException(
					"Select.call",
					"Usage: (" + getName() + " <goal>+)",
					vvec.toStringWithParens());
			}

			// Select first set of facts
			Fact goal = vvec.get(1).factValue(context);
			ValueVector facts = FactStorageManager.select(goal, context);

			// Append more sets of facts
			for (int i = 2; i < argc; i++) {
				goal = vvec.get(i).factValue(context);
				facts.addAll(FactStorageManager.select(goal, context));
			}

			// Done
			return new Value(facts, RU.LIST);
		}
	}

	/**
	 * FactWriter.assert and FactWriter.retract <i>Userfunction</i>s.
	 * 
	 * @author Thomas Barnekow
	 * @version 1.0
	 */
	class FactWriterFunction implements Userfunction {

		String m_name;

		FactWriterFunction(String name) {
			if (!name.equals(ASSERT) && !name.equals(RETRACT))
				throw new IllegalArgumentException(
					"Illegal function name: " + name);

			m_name = name;
		}

		public String getName() {
			return m_name;
		}

		/**
		 * Usage: (&lt;name&gt; &lt;fact&gt;+)
		 */
		public Value call(ValueVector vvec, Context context)
			throws JessException {
			// Check usage
			int size = vvec.size();
			if (size < 2)
				throw new JessException(
					"FactWriterFunction.call",
					"Usage: (" + getName() + " <fact>)",
					vvec.toStringWithParens());

			// Process first fact
			Value value = execute(vvec.get(1).factValue(context), context);
			if (size == 2)
				return value;

			// Process next facts
			ValueVector result = new ValueVector(size - 1);
			result.add(value);
			for (int i = 2; i < size; i++) {
				result.add(execute(vvec.get(i).factValue(context), context));
			}
			return new Value(result, RU.LIST);
		}

		/**
		 * Assert or retract, depending on function's name.
		 */
		Value execute(Fact fact, Context context) throws JessException {
			if (m_name.equals(ASSERT))
				return new FactIDValue(
					FactStorageManager.assertFact(fact, context));
			else
				return new FactIDValue(
					FactStorageManager.retract(fact, context));
		}
	}

	/**
	 * This Userfunction represents the MetaDataProvider.describe operation
	 */
	class Describe implements Userfunction {
		/**
		 * My name is Bond, James Bond...
		 */
		public String getName() {
			return DESCRIBE;
		}

		/**
		 * (fsm-describe &lt;entity&gt; [&lt;service&gt;])
		 * 
		 * @return {@link jess.Value}of type RU.EXTERNAL_ADDRESS containing a
		 *         {@link tb.provider.MetaObject}
		 */
		public Value call(ValueVector vvec, Context context)
			throws JessException {
			if (vvec.size() < 2)
				throw new JessException(
					"Describe.call",
					"Usage:",
					"(fsm-describe <entity> [<service>])");

			QualifiedName entity =
				new QualifiedName(vvec.get(1).stringValue(context));
			QualifiedName service = MetaDataProvider.DEFAULT_DESCRIPTION;
			if (vvec.size() >= 3)
				service = new QualifiedName(vvec.get(2).stringValue(context));

			return new Value(FactStorageManager.describe(entity, service));
		}
	}

	/**
	 * Add deftemplates
	 */
	class Define implements Userfunction {

		String m_name;

		Define(String name) {
			if (!name.equals(DEFINE)
				&& !name.equals(DEFINE_BACKCHAIN_REACTIVE))
				throw new IllegalArgumentException(name);
			m_name = name;
		}

		public String getName() {
			return m_name;
		}

		/**
		 * (fsm-define (&lt;java.util.MetaObject&gt; |
		 * &lt;multifield-value&lt;)*) <br>(fsm-define-backchain-reactive
		 * (&lt;java.util.MetaObject&gt; | &lt;multifield-value&lt;)*)
		 * 
		 * @return Funcall.TRUE
		 */
		public Value call(ValueVector vvec, Context context)
			throws JessException {
			define(vvec, 1, context);
			return Funcall.TRUE;
		}

		private void define(
			ValueVector container,
			int fromIndex,
			Context context)
			throws JessException {
			Rete engine = context.getEngine();
			for (int i = fromIndex; i < container.size(); i++) {
				Value value = container.get(i).resolveValue(context);
				switch (value.type()) {
					case RU.LIST :
						define(value.listValue(context), 0, context);
						break;
					case RU.EXTERNAL_ADDRESS :
						define(value.externalAddressValue(context), engine);
						break;
					default :
						throw new JessException(
							"AddDeftemplates.call",
							"Illegal parameter:",
							value.stringValue(context));
				}
			}
		}

		private void define(Object object, Rete engine) throws JessException {
			if (object instanceof Template) {
				Template template = (Template) object;
				engine.addDeftemplate((Deftemplate) template.getDeclaration());
				if (m_name.equals(DEFINE_BACKCHAIN_REACTIVE)) {
					FactStorageManager.doBackwardChaining(
						template.getQualifiedName(),
						engine);
				}
			} else if (object instanceof CompoundMetaObject) {
				Iterator iterator = ((CompoundMetaObject) object).iterator();
				while (iterator.hasNext())
					define(iterator.next(), engine);
			} else {
				throw new JessException(
					"Define.define(Object, Rete)",
					"Illegal class:",
					object.getClass().toString());
			}
		}
	}
}
